<?php
/* vim: set expandtab sw=4 ts=4 sts=4: */
/**
 * holds the PMA_List_Database class
 *
 * @version $Id: List_Database.class.php 10913 2007-11-09 21:24:40Z cybot_tm $
 */

/**
 * the list base class
 */
require_once './libraries/List.class.php';

/**
 * handles database lists
 *
 * <code>
 * $PMA_List_Database = new PMA_List_Database($userlink, $controllink);
 * </code>
 *
 * @todo this object should be attached to the PMA_Server object
 * @todo ? make use of INFORMATION_SCHEMA
 * @todo ? support --skip-showdatabases and user has only global rights
 * @access public
 * @since phpMyAdmin 2.9.10
 */
/*public*/ class PMA_List_Database extends PMA_List
{
	/**
	 * @var mixed   database link resource|object to be used
	 * @access protected
	 */
	var $_db_link = null;

	/**
	 * @var mixed   user database link resource|object
	 * @access protected
	 */
	var $_db_link_user = null;

	/**
	 * @var mixed   controluser database link resource|object
	 * @access protected
	 */
	var $_db_link_control = null;

	/**
	 * @var boolean whether SHOW DATABASES is disabled or not
	 * @access protected
	 */
	var $_show_databases_disabled = false;

	/**
	 * Constructor
	 *
	 * @uses	PMA_List_Database::$_db_link
	 * @uses	PMA_List_Database::$_db_link_user
	 * @uses	PMA_List_Database::$_db_link_control
	 * @uses	PMA_List_Database::build()
	 * @param   mixed   $db_link_user	   user database link resource|object
	 * @param   mixed   $db_link_control	control database link resource|object
	 */
	function __construct($db_link_user = null, $db_link_control = null) {
		$this->_db_link = $db_link_user;
		$this->_db_link_user = $db_link_user;
		$this->_db_link_control = $db_link_control;

		$this->build();
	}

	/**
	 * old PHP 4 style constructor
	 *
	 * @see PMA_List_Database::__construct()
	 */
	function PMA_List_Database($db_link_user = null, $db_link_control = null) {
		$this->__construct($db_link_user, $db_link_control);
	}

	/**
	 * removes all databases not accessible by current user from list
	 *
	 * @access  protected
	 * @uses	PMA_List_Database::$items
	 * @uses	PMA_List_Database::$_db_link_user
	 * @uses	PMA_List_Database::$_need_to_reindex to set it if reuqired
	 * @uses	PMA_DBI_select_db()
	 */
	function _checkAccess()
	{
		foreach ($this->items as $key => $db) {
			if (! @PMA_DBI_select_db($db, $this->_db_link_user)) {
				unset($this->items[$key]);
			}
		}

		// re-index values
		$this->_need_to_reindex = true;
	}

	/**
	 * checks if the configuration wants to hide some databases
	 *
	 * @todo temporaly use this docblock to test how to doc $GLOBALS
	 * @access  protected
	 * @uses	PMA_List_Database::$items
	 * @uses	PMA_List_Database::$_need_to_reindex to set it if reuqired
	 * @uses	preg_match()
	 * @uses	$cfg['Server']['hide_db']
	 */
	function _checkHideDatabase()
	{
		if (empty($GLOBALS['cfg']['Server']['hide_db'])) {
			return;
		}

		foreach ($this->items as $key => $db) {
			if (preg_match('/' . $GLOBALS['cfg']['Server']['hide_db'] . '/', $db)) {
				unset($this->items[$key]);
			}
		}
		// re-index values
		$this->_need_to_reindex = true;
	}

	/**
	 * retrieves database list from server
	 *
	 * @todo	we could also search mysql tables if all fail?
	 * @access  protected
	 * @uses	PMA_List_Database::$_show_databases_disabled for not retrying if SHOW DATABASES is disabled
	 * @uses	PMA_List_Database::$_db_link
	 * @uses	PMA_List_Database::$_db_link_control in case of SHOW DATABASES is disabled for userlink
	 * @uses	PMA_DBI_fetch_result()
	 * @uses	PMA_DBI_getError()
	 * @uses	$GLOBALS['error_showdatabases']
	 * @uses	$GLOBALS['errno']
	 * @param   string  $like_db_name   usally a db_name containing wildcards
	 */
	function _retrieve($like_db_name = '')
	{
		if ($this->_show_databases_disabled) {
			return array();
		}

		if (! empty($like_db_name)) {
			$like = " LIKE '" . $like_db_name . "';";
		} else {
			$like = ";";
		}

		$database_list = PMA_DBI_fetch_result('SHOW DATABASES' . $like, null, null, $this->_db_link);
		PMA_DBI_getError();

		if ($GLOBALS['errno'] !== 0) {
			// failed to get database list, try the control user
			// (hopefully there is one and he has SHOW DATABASES right)
			$this->_db_link = $this->_db_link_control;
			$database_list = PMA_DBI_fetch_result('SHOW DATABASES' . $like, null, null, $this->_db_link);

			PMA_DBI_getError();

			if ($GLOBALS['errno'] !== 0) {
				// failed! we will display a warning that phpMyAdmin could not safely
				// retrieve database list, the admin has to setup a control user or
				// allow SHOW DATABASES
				$GLOBALS['error_showdatabases'] = true;
				$this->_show_databases_disabled = true;
			}
		}

		return $database_list;
	}

	/**
	 * builds up the list
	 *
	 * @uses	PMA_List_Database::$items to initialize it
	 * @uses	PMA_List_Database::$_need_to_reindex
	 * @uses	PMA_List_Database::_checkOnlyDatabase()
	 * @uses	PMA_List_Database::_retrieve()
	 * @uses	PMA_List_Database::_checkHideDatabase()
	 * @uses	PMA_List_Database::_checkAccess()
	 * @uses	PMA_MYSQL_INT_VERSION
	 * @uses	array_values()
	 * @uses	natsort()
	 * @uses	$cfg['NaturalOrder']
	 */
	function build()
	{
		$this->items = array();

		if (! $this->_checkOnlyDatabase()) {
			$this->items = $this->_retrieve();
			if ($GLOBALS['cfg']['NaturalOrder']) {
				natsort($this->items);
				$this->_need_to_reindex = true;
			}
		}

		$this->_checkHideDatabase();

		// Before MySQL 4.0.2, SHOW DATABASES could send the
		// whole list, so check if we really have access:
		if (PMA_MYSQL_INT_VERSION < 40002) {
			$this->_checkAccess();
		}

		if ($this->_need_to_reindex) {
			$this->items = array_values($this->items);
		}
	}

	/**
	 * checks the only_db configuration
	 *
	 * @uses	PMA_List_Database::$_show_databases_disabled
	 * @uses	PMA_List_Database::$items
	 * @uses	PMA_List_Database::_retrieve()
	 * @uses	PMA_unescape_mysql_wildcards()
	 * @uses	preg_match()
	 * @uses	array_diff()
	 * @uses	array_merge()
	 * @uses	is_array()
	 * @uses	strlen()
	 * @uses	is_string()
	 * @uses	$cfg['Server']['only_db']
	 * @return  boolean false if there is no only_db, otherwise true
	 */
	function _checkOnlyDatabase()
	{
		if (is_string($GLOBALS['cfg']['Server']['only_db'])
		 && strlen($GLOBALS['cfg']['Server']['only_db'])) {
			$GLOBALS['cfg']['Server']['only_db'] = array(
				$GLOBALS['cfg']['Server']['only_db']
			);
		}

		if (! is_array($GLOBALS['cfg']['Server']['only_db'])) {
			return false;
		}

		foreach ($GLOBALS['cfg']['Server']['only_db'] as $each_only_db) {
			if ($each_only_db === '*' && ! $this->_show_databases_disabled) {
				// append all not already listed dbs to the list
				$this->items = array_merge($this->items,
					array_diff($this->_retrieve(), $this->items));
				// there can only be one '*', and this can only be last
				break;
			}

			// check if the db name contains wildcard,
			// thus containing not escaped _ or %
			if (! preg_match('/(^|[^\\\\])(_|%)/', $each_only_db)) {
				// ... not contains wildcard
				$this->items[] = PMA_unescape_mysql_wildcards($each_only_db);
				continue;
			}

			if (! $this->_show_databases_disabled) {
				$this->items = array_merge($this->items, $this->_retrieve($each_only_db));
				continue;
			}

			// @todo induce error, about not using wildcards with SHOW DATABASE disabled?
		}

		return true;
	}

	/**
	 * returns default item
	 *
	 * @uses	PMA_List::getEmpty()
	 * @uses	$GLOBALS['db']
	 * @uses	strlen()
	 * @return  string  default item
	 */
	function getDefault()
	{
		if (strlen($GLOBALS['db'])) {
			return $GLOBALS['db'];
		}

		return $this->getEmpty();
	}

	/**
	 * returns array with dbs grouped with extended infos
	 *
	 * @uses	$GLOBALS['PMA_List_Database']
	 * @uses	$GLOBALS['cfgRelation']['commwork']
	 * @uses	$cfg['ShowTooltip']
	 * @uses	$cfg['LeftFrameDBTree']
	 * @uses	$cfg['LeftFrameDBSeparator']
	 * @uses	$cfg['ShowTooltipAliasDB']
	 * @uses	PMA_getTableCount()
	 * @uses	PMA_getComments()
	 * @uses	is_array()
	 * @uses	implode()
	 * @uses	strstr()
	 * @uses	explode()
	 * @param   integer $offset
	 * @param   integer $count
	 * @return  array   db list
	 */
	function getGroupedDetails($offset, $count)
	{
		$dbgroups   = array();
		$parts	  = array();
		foreach ($this->getLimitedItems($offset, $count) as $key => $db) {
			// garvin: Get comments from PMA comments table
			$db_tooltip = '';
			if ($GLOBALS['cfg']['ShowTooltip']
			  && $GLOBALS['cfgRelation']['commwork']) {
				$_db_tooltip = PMA_getComments($db);
				if (is_array($_db_tooltip)) {
					$db_tooltip = implode(' ', $_db_tooltip);
				}
			}

			if ($GLOBALS['cfg']['LeftFrameDBTree']
				&& $GLOBALS['cfg']['LeftFrameDBSeparator']
				&& strstr($db, $GLOBALS['cfg']['LeftFrameDBSeparator']))
			{
				// use strpos instead of strrpos; it seems more common to
				// have the db name, the separator, then the rest which
				// might contain a separator
				// like dbname_the_rest
				$pos			= strpos($db, $GLOBALS['cfg']['LeftFrameDBSeparator']);
				$group		  = substr($db, 0, $pos);
				$disp_name_cut  = substr($db, $pos);
			} else {
				$group		  = $db;
				$disp_name_cut  = $db;
			}

			$disp_name  = $db;
			if ($db_tooltip && $GLOBALS['cfg']['ShowTooltipAliasDB']) {
				$disp_name	  = $db_tooltip;
				$disp_name_cut  = $db_tooltip;
				$db_tooltip	 = $db;
			}

			$dbgroups[$group][$db] = array(
				'name'		  => $db,
				'disp_name_cut' => $disp_name_cut,
				'disp_name'	 => $disp_name,
				'comment'	   => $db_tooltip,
				'num_tables'	=> PMA_getTableCount($db),
			);
		} // end foreach ($GLOBALS['PMA_List_Database']->items as $db)
		return $dbgroups;
	}

	/**
	 * returns a part of the items 
	 *
	 * @uses	PMA_List_Database::$items
	 * @uses	array_slice()
	 * @param   integer $offset
	 * @param   integer $count
	 * @return  array  some items 
	 */
	function getLimitedItems($offset, $count)
	{
		return(array_slice($this->items, $offset, $count));
	}

	/**
	 * returns html code for list with dbs
	 *
	 * @return  string  html code list
	 */
	function getHtmlListGrouped($selected = '', $offset, $count)
	{
		if (true === $selected) {
			$selected = $this->getDefault();
		}

		$return = '<ul id="databaseList" xml:lang="en" dir="ltr">' . "\n";
		foreach ($this->getGroupedDetails($offset, $count) as $group => $dbs) {
			if (count($dbs) > 1) {
				$return .= '<li>' . htmlspecialchars($group) . '<ul>' . "\n";
				// wether display db_name cuted by the group part
				$cut = true;
			} else {
				// .. or full
				$cut = false;
			}
			foreach ($dbs as $db) {
				$return .= '<li';
				if ($db['name'] == $selected) {
					$return .= ' class="selected"';
				}
				$return .= '><a';
				if (! empty($db['comment'])) {
					$return .= ' title="' . htmlspecialchars($db['comment']) . '"';
				}
				$return .= ' href="index.php?' . PMA_generate_common_url($db['name'])
					. '" target="_parent">';
				if ($cut) {
					$return .= htmlspecialchars($db['disp_name_cut']);
				} else {
					$return .= htmlspecialchars($db['disp_name']);
				}
				$return .= ' (' . $db['num_tables'] . ')';
				$return .= '</a></li>' . "\n";
			}
			if (count($dbs) > 1) {
				$return .= '</ul></li>' . "\n";
			}
		}
		$return .= '</ul>';

		return $return;
	}

	/**
	 * returns html code for select form element with dbs
	 *
	 * @todo IE can not handle different text directions in select boxes so,
	 * as mostly names will be in english, we set the whole selectbox to LTR
	 * and EN
	 *
	 * @return  string  html code select
	 */
	function getHtmlSelectGrouped($selected = '', $offset, $count)
	{
		if (true === $selected) {
			$selected = $this->getDefault();
		}

		$return = '<select name="db" id="lightm_db" xml:lang="en" dir="ltr"'
			. ' onchange="if (this.value != \'\') window.parent.openDb(this.value);">' . "\n"
			. '<option value="" dir="' . $GLOBALS['text_dir'] . '">'
			. '(' . $GLOBALS['strDatabases'] . ') ...</option>' . "\n";
		foreach ($this->getGroupedDetails($offset, $count) as $group => $dbs) {
			if (count($dbs) > 1) {
				$return .= '<optgroup label="' . htmlspecialchars($group)
					. '">' . "\n";
				// wether display db_name cuted by the group part
				$cut = true;
			} else {
				// .. or full
				$cut = false;
			}
			foreach ($dbs as $db) {
				$return .= '<option value="' . htmlspecialchars($db['name']) . '"'
					.' title="' . htmlspecialchars($db['comment']) . '"';
				if ($db['name'] == $selected) {
					$return .= ' selected="selected"';
				}
				$return .= '>' . htmlspecialchars($cut ? $db['disp_name_cut'] : $db['disp_name'])
					.' (' . $db['num_tables'] . ')</option>' . "\n";
			}
			if (count($dbs) > 1) {
				$return .= '</optgroup>' . "\n";
			}
		}
		$return .= '</select>';

		return $return;
	}

	/**
	 * this is just a backup, if all is fine this can be deleted later
	 *
	 * @deprecated
	 * @access protected
	 */
	function _checkAgainstPrivTables()
	{
		// 1. get allowed dbs from the "mysql.db" table
		// lem9: User can be blank (anonymous user)
		$local_query = "
			SELECT DISTINCT `Db` FROM `mysql`.`db`
			WHERE `Select_priv` = 'Y'
			AND `User`
			IN ('" . PMA_sqlAddslashes($GLOBALS['cfg']['Server']['user']) . "', '')";
		$tmp_mydbs = PMA_DBI_fetch_result($local_query, null, null,
			$GLOBALS['controllink']);
		if ($tmp_mydbs) {
			// Will use as associative array of the following 2 code
			// lines:
			//   the 1st is the only line intact from before
			//	 correction,
			//   the 2nd replaces $dblist[] = $row['Db'];

			// Code following those 2 lines in correction continues
			// populating $dblist[], as previous code did. But it is
			// now populated with actual database names instead of
			// with regular expressions.
			var_dump($tmp_mydbs);
			$tmp_alldbs = PMA_DBI_query('SHOW DATABASES;', $GLOBALS['controllink']);
			// loic1: all databases cases - part 2
			if (isset($tmp_mydbs['%'])) {
				while ($tmp_row = PMA_DBI_fetch_row($tmp_alldbs)) {
					$dblist[] = $tmp_row[0];
				} // end while
			} else {
				while ($tmp_row = PMA_DBI_fetch_row($tmp_alldbs)) {
					$tmp_db = $tmp_row[0];
					if (isset($tmp_mydbs[$tmp_db]) && $tmp_mydbs[$tmp_db] == 1) {
						$dblist[]		   = $tmp_db;
						$tmp_mydbs[$tmp_db] = 0;
					} elseif (!isset($dblist[$tmp_db])) {
						foreach ($tmp_mydbs as $tmp_matchpattern => $tmp_value) {
							// loic1: fixed bad regexp
							// TODO: db names may contain characters
							//	   that are regexp instructions
							$re		= '(^|(\\\\\\\\)+|[^\])';
							$tmp_regex = ereg_replace($re . '%', '\\1.*', ereg_replace($re . '_', '\\1.{1}', $tmp_matchpattern));
							// Fixed db name matching
							// 2000-08-28 -- Benjamin Gandon
							if (ereg('^' . $tmp_regex . '$', $tmp_db)) {
								$dblist[] = $tmp_db;
								break;
							}
						} // end while
					} // end if ... elseif ...
				} // end while
			} // end else
			PMA_DBI_free_result($tmp_alldbs);
			unset($tmp_mydbs);
		} // end if

		// 2. get allowed dbs from the "mysql.tables_priv" table
		$local_query = 'SELECT DISTINCT Db FROM mysql.tables_priv WHERE Table_priv LIKE \'%Select%\' AND User = \'' . PMA_sqlAddslashes($GLOBALS['cfg']['Server']['user']) . '\'';
		$rs		  = PMA_DBI_try_query($local_query, $GLOBALS['controllink']);
		if ($rs && @PMA_DBI_num_rows($rs)) {
			while ($row = PMA_DBI_fetch_assoc($rs)) {
				if (!in_array($row['Db'], $dblist)) {
					$dblist[] = $row['Db'];
				}
			} // end while
			PMA_DBI_free_result($rs);
		} // end if
	}
}
?>
